package org.javahispano.javacup.tacticas_aceptadas.pablo_blazq.com.blame.javacup;

import org.javahispano.javacup.modelo.*;

import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Vector;

public class HortalezaBlamers implements Tactica {

	// constantes
	private static final double ANGULO_GOLPEO_DESPEJE = 45;
	private static final double ANGULO_GOLPEO_CUELGUE = 45;
	private static final double ANGULO_GOLPEO_PASE = 0;
	private static final double ANGULO_GOLPEO_TIRO = 20;
	private static final double FUERZA_GOLPEO_MAXIMA = 1;
	private static final double FUERZA_GOLPEO_MEDIA = 0.5;
	private static final double FACTOR_MOVIMIENTO_PORTERO = 1.1;
	private static final double VELOCIDAD_RECEPCION_PASE = 0.5;
	private static final Posicion POSICION_SAQUE_PUERTA_IZDA = new Posicion(-Constantes.LARGO_AREA_CHICA / 2, -(Constantes.LARGO_CAMPO_JUEGO / 2 - Constantes.ANCHO_AREA_CHICA));
	private static final Posicion POSICION_SAQUE_PUERTA_DCHA = new Posicion(Constantes.LARGO_AREA_CHICA / 2, -(Constantes.LARGO_CAMPO_JUEGO / 2 - Constantes.ANCHO_AREA_CHICA));
	private static final Posicion POSICION_CENTRO_CAMPO_PROPIO = new Posicion(0, -(Constantes.LARGO_CAMPO_JUEGO / 4));
	private static final int UMBRAL_ITERACIONES_SIN_PODER_REMATAR = 200;
	private static final double POSICIONES_PASE_MARGEN_IZDO = 4;
	private static final double POSICIONES_PASE_MARGEN_INF = 2.5;
	private static final double DISTANCIA_POSICIONES_PASE = 5;
	
	// variables de detalle de jugadores y de disposicion de jugadores
	private TacticaDetalle detalle = new HortalezaBlamersDetalle();
	private Posicion alineacionEnJuego[] = Alineacion.alineacionEnJuego;
	private Posicion alineacionSaca[] = Alineacion.alineacionSaca;
	private Posicion alineacionRecibe[] = Alineacion.alineacionRecibe;
	private Posicion[] zonasLD = Alineacion.zonasLD;
	private Posicion[] zonasRU = Alineacion.zonasRU;

	// variables
	private LinkedList<Comando> comandos = new LinkedList<Comando>();
	private int indicePorteroPropio;
	private int indicePorteroRival = -1;
	private Posicion posicionBalonAnterior;
	private Double direccionBalonAnterior;
	private Vector<Posicion3D> posicionesBalonCalculo;
	private int iteracionesSinPoderRematar = 0;
	private int intentosDeAcumularPosesion = 0;
	private Posicion[] posicionesPase;
	
	/**
	 * Constructor que inicializa el indice del portero propio, para 
	 * tenerlo identificado 
	 */
	public HortalezaBlamers() {
		super();

		// obtenemos el indice del portero propio
		for(int i = 0; i < detalle.getJugadores().length; i++)
			if(detalle.getJugadores()[i].esPortero()) 
				indicePorteroPropio = i;
		
		// rellena las posiciones de pase al hueco
		int dimensionX = (int) Math.ceil((Constantes.ANCHO_CAMPO_JUEGO - POSICIONES_PASE_MARGEN_IZDO) / DISTANCIA_POSICIONES_PASE);
		int dimensionY = (int) Math.ceil((Constantes.LARGO_CAMPO_JUEGO - POSICIONES_PASE_MARGEN_INF) / DISTANCIA_POSICIONES_PASE);
		posicionesPase = new Posicion[dimensionX * dimensionY];
		double inicioX = (- Constantes.ANCHO_CAMPO_JUEGO / 2) + POSICIONES_PASE_MARGEN_IZDO;
		double inicioY = (- Constantes.LARGO_CAMPO_JUEGO / 2) + POSICIONES_PASE_MARGEN_INF;
		int indiceInsercion = 0;
		for(double i = inicioX; i < Constantes.ANCHO_CAMPO_JUEGO / 2; i += DISTANCIA_POSICIONES_PASE)
			for(double j = inicioY; j < Constantes.LARGO_CAMPO_JUEGO / 2; j += DISTANCIA_POSICIONES_PASE)
				posicionesPase[indiceInsercion++] = new Posicion(i, j);
	}

	/**
	 * Metodos para el framework
	 */
	public Posicion[] getPosicionSaca(SituacionPartido sp) {
		return alineacionSaca;
	}
	public Posicion[] getPosicionRecive(SituacionPartido sp) {
		return alineacionRecibe;
	}
	public TacticaDetalle getDetalle() {
		return detalle;
	}

	/**
	 * Metodo de entrada para cada iteracion
	 */
	public List<Comando> ejecutar(SituacionPartido sp) {
		try {
			comandos.clear();

			// log para ver por donde va el balon
			//System.out.println("it:" + sp.iteracion() + " Balon en " + sp.balon() + "@" + sp.alturaBalon());

			// obtenemos el indice del portero rival solo la primera vez
			if(indicePorteroRival == -1)
				for(int i = 0; i < sp.detalleJugadoresRivales().length; i++)
					if(sp.detalleJugadoresRivales()[i].esPortero())
						indicePorteroRival = i;

			// movimiento de los jugadores
			movimientoBascular(sp.balon());

			// movimiento del jugador que recogera el balon
			correrHaciaBalon(sp);
			
			// pases y tiros
			rematarBalon(sp);
			
			// comprobacion para desmontar a equipos acumuladores de posesion
			desmontarEquiposAcumuladoresDePosesion(sp);

			return comandos;
		} 
		catch (Exception e) {
			e.printStackTrace();
			comandos.clear();
			return comandos;
		}
	}

	/**
	 * Comprueba cuanto tiempo llevan sin poder rematar, y si supera un umbral, manda
	 * a los dos jugadores mas cercanos al balon directos a por el, sin bascular
	 * @param sp
	 */
	private void desmontarEquiposAcumuladoresDePosesion(SituacionPartido sp) {
		
		if(sp.puedenRematar().length == 0)
			iteracionesSinPoderRematar++;
		else
			iteracionesSinPoderRematar = 0;
		
		if(iteracionesSinPoderRematar > UMBRAL_ITERACIONES_SIN_PODER_REMATAR) {
			int indiceJugador1 = 0;
			int indiceJugador2 = 0;
			for(int i = 0; i < sp.misJugadores().length; i++)
				if(sp.balon().distancia(sp.misJugadores()[i]) < sp.balon().distancia(sp.misJugadores()[indiceJugador1]))
					indiceJugador1 = i;
			
			for(int i = 0; i < sp.misJugadores().length; i++)
				if(i != indiceJugador1 &&
						sp.balon().distancia(sp.misJugadores()[i]) < sp.balon().distancia(sp.misJugadores()[indiceJugador2]))
					indiceJugador2 = i;

			intentosDeAcumularPosesion++;
			comandos.add(new ComandoIrA(indiceJugador1, sp.balon()));
			comandos.add(new ComandoIrA(indiceJugador2, sp.balon()));
			//System.out.println("Intento numero " + intentosDeAcumularPosesion + " de acumular posesion. Jugadores " + (indiceJugador1 + 1) + " y " + (indiceJugador2 + 1) + " van a por el balon");
		}
	}

	/**
	 * Comportamiento de los pases y tiros. Primero los remates a balon parado, 
	 * y despues los remates en juego, si no toca sacar
 	 * @param sp
	 */
	private void rematarBalon(SituacionPartido sp) {
		
		if(sp.saco()) {
			rematarBalonParado(sp);
		}
		else {
			boolean empatando = sp.golesPropios() == sp.golesRival();
			boolean menosDe2GolesPropios = sp.golesPropios() < 2;
			boolean masDe2MinutosJugados = sp.iteracion() > Constantes.ITERACIONES * 0.66;
			boolean ganoEnPosesionPorMasDe5Puntos = false;
			
			// si voy empatando, he metido menos de 2 goles en 2 minutos, y no gano en posesion
			// por mas de 5 puntos, necesito hacer pases para recuperar posesion
			if(empatando && menosDe2GolesPropios && masDe2MinutosJugados && !ganoEnPosesionPorMasDe5Puntos) {
				// el primero que pueda rematar remata. Primero los remates a balon parado, y despues
				// los remates en juego, si no ha habido remates a balon parado
				rematarBalonEnJuego(sp, true);
			}
			// si no, me comporto normalmente
			else {
				// el primero que pueda rematar remata. Primero los remates a balon parado, y despues
				// los remates en juego, si no ha habido remates a balon parado
				rematarBalonEnJuego(sp, false);
			}
		}
	}

	/**
	 * Realiza el golpeo a balon parado (corners, saques de puerta, saques de centro)
	 * @param sp
	 */
	private void rematarBalonParado(SituacionPartido sp) {
		
		int rematan[] = sp.puedenRematar();
		if(rematan.length > 0) {
			// para el saque de puerta, saca al centro del campo, con un angulo d 45� para evitar rivales
			if(sp.balon().equals(POSICION_SAQUE_PUERTA_IZDA) || sp.balon().equals(POSICION_SAQUE_PUERTA_DCHA)) {
				comandos.add(new ComandoGolpearBalon(rematan[0], Constantes.centroCampoJuego, FUERZA_GOLPEO_MAXIMA, ANGULO_GOLPEO_DESPEJE));
			}
			// para el saque de centro, saca al centro del campo propio
			else if(sp.balon().equals(Constantes.centroCampoJuego)) { 
				comandos.add(new ComandoGolpearBalon(rematan[0], POSICION_CENTRO_CAMPO_PROPIO, FUERZA_GOLPEO_MEDIA, ANGULO_GOLPEO_PASE));
			}
			// para el saque de corner, saca al punto de penalty
			else if(sp.balon().equals(Constantes.cornerSupIzq) || sp.balon().equals(Constantes.cornerSupDer)) { 
				comandos.add(new ComandoGolpearBalon(rematan[0], Constantes.penalSup, FUERZA_GOLPEO_MAXIMA, ANGULO_GOLPEO_CUELGUE));
			}
			// para saques de banda
			else { 
				comandos.add(new ComandoGolpearBalon(rematan[0], Constantes.penalSup, FUERZA_GOLPEO_MAXIMA, ANGULO_GOLPEO_CUELGUE));
			}
		}	
	}

	/**
	 * Realiza el golpeo del balon cuando este esta en juego segun lo siguiente:
	 * Si es el portero despeja al centro del campo con 45� para evitar a los delanteros
	 * Si no, si tiene un pase para hacer, lo hace
	 * Si no, si tiene un tiro para hacer, lo hace
	 * Si no, despeja al campo contrario 
	 * @param sp
	 * @param necesarioMejorarPosesion 
	 */
	private void rematarBalonEnJuego(SituacionPartido sp, boolean necesarioMejorarPosesion) {
	
		int rematan[] = sp.puedenRematar();
		if(rematan.length > 0) {
			Integer indiceMejorPase = calcularIndiceMejorPase(sp, rematan[0], necesarioMejorarPosesion);
			Double direccionRemate = calcularDireccionRemate(sp);

			// despeja si es el portero
			if(rematan[0] == indicePorteroPropio) {
				// el portero despeja con 45� para evitar a los delanteros
				comandos.add(new ComandoGolpearBalon(rematan[0], Constantes.centroCampoJuego, FUERZA_GOLPEO_MAXIMA, ANGULO_GOLPEO_DESPEJE));
				return;
			}
			// si puede pasar, pasa
			if(indiceMejorPase != null) {
				double direccionPase = calcularDireccion(sp.misJugadores()[rematan[0]], sp.misJugadores()[indiceMejorPase]);
//				double fuerzaRemateAbsoluta = (1.0 * Constantes.FACTOR_DISMINUCION_VEL_BALON_SUELO) + 
//				(sp.misJugadores()[rematan[0]].distancia(sp.misJugadores()[indiceMejorPase]) * (1 - Constantes.FACTOR_DISMINUCION_VEL_BALON_SUELO));
//				double fuerzaRemateRelativa = fuerzaRemateAbsoluta / Constantes.getVelocidadRemate(sp.detalleMisJugadores()[rematan[0]].getRemate());
//				comandos.add(new ComandoGolpearBalon(rematan[0], convertirRadianesAGrados(direccionPase), fuerzaRemateRelativa, ANGULO_GOLPEO_PASE));
				comandos.add(new ComandoGolpearBalon(rematan[0], convertirRadianesAGrados(direccionPase), FUERZA_GOLPEO_MAXIMA, ANGULO_GOLPEO_PASE));
				return;
			}
			// si puede tirar a puerta, tira
			if(direccionRemate != null) {
				comandos.add(new ComandoGolpearBalon(rematan[0], convertirRadianesAGrados(direccionRemate), FUERZA_GOLPEO_MAXIMA, ANGULO_GOLPEO_TIRO));
				return;
			}
			// si no pasa ni tira, lanza el balon al centro de la porteria rival
			// TODO: realizar una accion mejor
			comandos.add(new ComandoGolpearBalon(rematan[0], Constantes.centroArcoSup, FUERZA_GOLPEO_MAXIMA, ANGULO_GOLPEO_TIRO));
		}
	}

	/**
	 * Realiza la conversion de radianes a grados
	 * @param direccion
	 * @return
	 */
	private double convertirRadianesAGrados(double direccion) {
		return (direccion / Math.PI) * 180;
	}

	/**
	 * Calcula cuales son los jugadores que estan lo suficientemente cerca del pasador, por delante
	 * suya y relativamente descubiertos.
	 * Devuelve el indice del jugador mas cercano de los anteriores, y null si no hay pase claro
	 * @param sp
	 * @param indicePasador 
	 * @param necesarioMejorarPosesion 
	 * @return
	 */
	private Integer calcularIndiceMejorPase(SituacionPartido sp, int indicePasador, boolean necesarioMejorarPosesion) {

		// analizo la posicion de cada jugador 
		Integer indiceMejorPase = null;
		for(int i = 0; i < sp.detalleMisJugadores().length; i++) {
			if(i == indicePasador)
				continue;
			
			// calculo la fuerza con la que debe pasar a ese jugador
			// V = M*f + d(1-f)
			double fuerzaRemate = (VELOCIDAD_RECEPCION_PASE * Constantes.FACTOR_DISMINUCION_VEL_BALON_SUELO) + 
			(sp.misJugadores()[indicePasador].distancia(sp.misJugadores()[i]) * (1 - Constantes.FACTOR_DISMINUCION_VEL_BALON_SUELO));
			
			boolean estaLejos = Constantes.getVelocidadRemate(sp.detalleMisJugadores()[indicePasador].getRemate()) < fuerzaRemate;
			boolean estaDetras = sp.misJugadores()[i].getY() < sp.misJugadores()[indicePasador].getY();
			boolean estaEnZonaCentral = 
				sp.misJugadores()[i].getY() > - Constantes.LARGO_CAMPO_JUEGO / 4 &&
				sp.misJugadores()[i].getY() < Constantes.LARGO_CAMPO_JUEGO / 4;
			
			// comprobamos que rivales estan taponando el posible pase
			boolean estaCubierto = false;
			for(int j = 0; j < sp.rivales().length; j++) {
				double direccionCompanero = this.calcularDireccion(sp.misJugadores()[indicePasador], sp.misJugadores()[i]);
				double direccionRival = this.calcularDireccion(sp.misJugadores()[indicePasador], sp.rivales()[j]);
				boolean estaRivalMasCerca = sp.misJugadores()[indicePasador].distancia(sp.rivales()[j]) <
				                            sp.misJugadores()[indicePasador].distancia(sp.misJugadores()[i]);
				
				if(estaRivalMasCerca && calcularAngulo(direccionCompanero, direccionRival) < (Math.PI / 8)) { //Constante 22.5
					estaCubierto = true;
					break;
				}
			}
			
			boolean condicionDePase;
			if(necesarioMejorarPosesion)
				condicionDePase = !estaLejos && !estaCubierto && estaEnZonaCentral;
			else
				condicionDePase = !estaLejos && !estaCubierto && !estaDetras;
			// sera un pase adecuado si
			// no esta lejos, no esta cubierto y no esta detras (esto ultimo a no ser que haya
			// que recuperar posesion
			if(condicionDePase) {
				if(indiceMejorPase == null)
					indiceMejorPase = i;
				else {
					if(sp.misJugadores()[indicePasador].distancia(sp.misJugadores()[i]) < 
							sp.misJugadores()[indicePasador].distancia(sp.misJugadores()[indiceMejorPase]))
						indiceMejorPase = i;
				}
			}
		}
		return indiceMejorPase;
	}

	/**
	 * Calcula las posiciones por las que va a pasar el balon,
	 * y la primera posicion a la que llegue un jugador, es hacia la que va a correr
	 * @param sp
	 */
	private void correrHaciaBalon(SituacionPartido sp) {

		// si saco el balon, el jugador mas cercano va al punto de saque
		if(sp.saco()) {
			int indMasCercano = sp.balon().indiceMasCercano(sp.misJugadores());
			comandos.add(new ComandoIrA(indMasCercano, sp.balon()));
		}
		// si no, calcula las posiciones del balon y quien ira a golpearlo
		else {
			Vector<Posicion3D> posicionesBalon = calcularDestinoBalon(sp.balon(), sp.alturaBalon(), sp.iteracion());
			if(posicionesBalon != null) {
				Iterator<Posicion3D> itPosicionesBalon = posicionesBalon.iterator();
				boolean comandoLanzado = false;
				while(itPosicionesBalon.hasNext() && !comandoLanzado) {
					Posicion3D p3d = itPosicionesBalon.next();
					// solo consideramos las posiciones futuras
					if(p3d.getIteracion() > sp.iteracion()) {
						JugadorDetalle[] jugadores = sp.detalleMisJugadores();
						for(int i = 0; i < jugadores.length && !comandoLanzado; i++) {
							if(alcanzaJugadorPosicion3D(jugadores[i], sp.misJugadores()[i], p3d, sp.iteracion())) {
								comandos.add(new ComandoIrA(i, p3d.getPosicion()));
								comandoLanzado = true;
							}
						}
					}
				}
			}
		}
	}

	/**
	 * Devuelve true solo si el jugador indicado puede llegar a la posicion balon en la iteracion
	 * en la el balon estar� en esa posicion, o antes
	 * @param jd
	 * @param posicionJugador
	 * @param p3d
	 * @param iteracion
	 * @return
	 */
	private boolean alcanzaJugadorPosicion3D(JugadorDetalle jd, Posicion posicionJugador, Posicion3D p3d, int iteracion) {

		double distanciaEspacio = posicionJugador.distancia(p3d.getPosicion());
		double distanciaTiempo = p3d.getIteracion() - iteracion;

		boolean balonEnAreaPropia = dentroAreaPropia(p3d.getPosicion());
		boolean esPortero = jd.esPortero();
		
		if(esPortero && balonEnAreaPropia) {
			boolean alcanzaEnSuelo = distanciaEspacio <= (jd.getVelocidad() * distanciaTiempo) + Constantes.DISTANCIA_CONTROL_BALON_PORTERO;
			boolean alcanzaEnAltura = p3d.getAltura() <= Constantes.ALTO_ARCO;
			return alcanzaEnSuelo && alcanzaEnAltura;
		}
		else {
			boolean alcanzaEnSuelo = distanciaEspacio <= (jd.getVelocidad() * distanciaTiempo) + Constantes.DISTANCIA_CONTROL_BALON;
			boolean alcanzaEnAltura = p3d.getAltura() <= Constantes.ALTURA_CONTROL_BALON;
			return alcanzaEnSuelo && alcanzaEnAltura;
		}
	}

	/**
     * Devuelve true cuando la posicion esta dentro del area propia
	 * @param p
	 * @return
	 */
    private boolean dentroAreaPropia(Posicion p) {
        return (p.getX() >= - Constantes.LARGO_AREA_GRANDE / 2 && p.getX() <= Constantes.LARGO_AREA_GRANDE / 2 &&
                p.getY() >= - Constantes.LARGO_CAMPO_JUEGO / 2 && p.getY() <= (-Constantes.LARGO_CAMPO_JUEGO / 2) + Constantes.ANCHO_AREA_GRANDE);
    }

	/**
	 * Si el balon fue golpeado en la iteracion anterior, calcula una bateria de posiciones que tomara
	 * el balon en las siguientes iteraciones (posicion, altura e iteracion)
	 * @param posicionBalon
	 * @param alturaBalon
	 * @param iteracion
	 * @return
	 */
	private Vector<Posicion3D> calcularDestinoBalon(Posicion posicionBalon, Double alturaBalon, int iteracion) {

		// la primera vez no se puede calcular la direccion del balon
		if(posicionBalonAnterior != null) {

			// iniciamos el calculo solo si la direccion vario con respecto a la direccion anterior
			Double direccionBalon = calcularDireccion(posicionBalonAnterior, posicionBalon);
			if(		(direccionBalonAnterior == null) || 
					(Math.abs(direccionBalon - direccionBalonAnterior) > (Math.PI * 2) / 1000)) {
				direccionBalonAnterior = direccionBalon;
				posicionesBalonCalculo = new Vector<Posicion3D>();
				// inicializamos las variables como si el balon acabara de ser golpeado
				Posicion posicionBalonCalculo = new Posicion(posicionBalonAnterior);
				double alturaBalonCalculo = 0;
				boolean suelo = false;
				double balonDx = posicionBalon.getX() - posicionBalonAnterior.getX();
				double balonDy = posicionBalon.getY() - posicionBalonAnterior.getY();
				double balonDz;
				if(alturaBalon == 0)
					balonDz = 0;
				else
					balonDz = alturaBalon;
				
				// en el bucle actualizamos la posicion de calculo para cada iteracion
				// hasta que el desplazamiento del balon se reduzca al minimo
				while(Math.hypot(balonDx, balonDy) > 0.01) {
					// determina si el balon se mueve por el suelo o por el aire
			        if (!suelo && alturaBalonCalculo == 0 && balonDz >= 0 && balonDz < Constantes.G * 3) {
			            suelo = true;
			        }
			        if (suelo) {
			        	posicionBalonCalculo = posicionBalonCalculo.moverPosicion(balonDx, balonDy);
			            balonDx = balonDx * Constantes.FACTOR_DISMINUCION_VEL_BALON_SUELO;
			            balonDy = balonDy * Constantes.FACTOR_DISMINUCION_VEL_BALON_SUELO;
			        } 
			        else {
			        	alturaBalonCalculo = redondeaMultiplo(alturaBalonCalculo + balonDz, Constantes.G);
			            posicionBalonCalculo = posicionBalonCalculo.moverPosicion(balonDx, balonDy);
			            balonDz = redondeaMultiplo(balonDz - Constantes.G, Constantes.G);
			            balonDx = balonDx * Constantes.FACTOR_DISMINUCION_VEL_BALON_AIRE;
			            balonDy = balonDy * Constantes.FACTOR_DISMINUCION_VEL_BALON_AIRE;
			            if(alturaBalonCalculo == 0) {
			                balonDz = (-balonDz - Constantes.G) * Constantes.FACTOR_DISMINUCION_ALTURA_BALON_REBOTE;
			                balonDz = redondeaMultiplo(balonDz, Constantes.G);
			            }
			        }
			        posicionesBalonCalculo.add(new Posicion3D(posicionBalonCalculo, alturaBalonCalculo, iteracion++));
				}
			}
		}
		posicionBalonAnterior = new Posicion(posicionBalon);
		return posicionesBalonCalculo;
	}

	/**
	 * Redondea al valor mas cercano del divisor
	 * @param valor
	 * @param divisor
	 * @return
	 */
	private double redondeaMultiplo(double valor, double divisor) {
        return Math.round(valor / divisor) * divisor;
    }

	/**
	 * Calcula el angulo horizontal de remate en funcion de la posicion del balon
	 * y la posicion del portero rival 
	 * @param sp
	 * @return
	 */
	private Double calcularDireccionRemate(SituacionPartido sp) {
		double direccionRemate;

		Posicion posicionBalon = sp.balon();
		Posicion posicionPorteroRival = sp.rivales()[indicePorteroRival];

		// calcular el angulo a los palos, al porteria y el alcance del portero por izda y dcha
		double direccionPaloIzdo = calcularDireccion(posicionBalon, Constantes.posteIzqArcoSup);
		double direccionPaloDcho = calcularDireccion(posicionBalon, Constantes.posteDerArcoSup);
		double direccionPortero = calcularDireccion(posicionBalon, posicionPorteroRival);
		double dx = posicionPorteroRival.getX() - posicionBalon.getX();
		double dy = posicionPorteroRival.getY() - posicionBalon.getY();
		double anguloAlcancePortero = Math.atan2(
				FACTOR_MOVIMIENTO_PORTERO * Constantes.DISTANCIA_CONTROL_BALON_PORTERO,
				Math.hypot(dx, dy));

		double direccionAlcancePorteroIzda = direccionPortero + anguloAlcancePortero;
		double direccionAlcancePorteroDcha = direccionPortero - anguloAlcancePortero;

		// calculamos el ancho de la zona de remate por la izda y dcha
		double anguloRemateIzda = calcularAngulo(direccionPaloIzdo, direccionAlcancePorteroIzda);
		double anguloRemateDcha = calcularAngulo(direccionAlcancePorteroDcha, direccionPaloIzdo);

		// si no hay zona de remate por la izda y dcha, devuelve null
		if((anguloRemateIzda < 0) && (anguloRemateDcha < 0)) {
			return null;
		}
		// si lo hay, remata al hueco de la izda o de la dcha
		else {
			// si el mayor hueco esta a la izda
			if(anguloRemateIzda > anguloRemateDcha) {
				// si el portero esta fuera de la porteria
				if(anguloRemateIzda > calcularAngulo(direccionPaloIzdo, direccionPaloDcho))
					direccionRemate = calcularDireccion(posicionBalon, Constantes.centroArcoSup);
				else
					direccionRemate = (direccionPaloIzdo + direccionAlcancePorteroIzda) / 2;
			}
			// si el mayor hueco esta a la dcha
			else {
				// si el portero esta fuera de la porteria
				if(anguloRemateDcha > calcularAngulo(direccionPaloIzdo, direccionPaloDcho))
					direccionRemate = calcularDireccion(posicionBalon, Constantes.centroArcoSup);
				else
					direccionRemate = (direccionPaloDcho + direccionAlcancePorteroDcha) / 2;
			}
		}
		
		return direccionRemate;
	}

	/**
	 * Calcula una direccion entre dos posiciones
	 * @param posicionReferencia
	 * @param posicionCalculo
	 * @return
	 */
	private double calcularDireccion(Posicion posicionReferencia, Posicion posicionCalculo) {
		return Math.atan2(
				posicionCalculo.getY() - posicionReferencia.getY(), 
				posicionCalculo.getX() - posicionReferencia.getX());
	}

	/**
	 * Calcula un angulo entre dos direcciones
	 * @param d1
	 * @param d2
	 * @return
	 */
	private double calcularAngulo(double d1, double d2) {
		
		double angulo = Math.max(d1, d2) - Math.min(d1, d2);
		if(angulo > Math.PI)
			angulo = (2 * Math.PI) - angulo;
		
		return angulo;
	}
	
	/**
	 * Para el movimiento bascular del equipo
	 * @param comandos2
	 * @param posicionBalon
	 */
	private void movimientoBascular(Posicion posicionBalon) {

		for (int i = 0; i < 11; i++) {
			Posicion p;
			if(i == indicePorteroPropio)
				p = movimientoBascularPortero(posicionBalon);
			else
				p = movimientoBascularJugador(i, posicionBalon);
			
			comandos.add(new ComandoIrA(i, p));
		}
	}

	/**
	 * Esta funcion calcula la posicion que deber� ocupar el jugador en el campo, en funcion de
	 * un punto sobre el que pivota, y un rectangulo de maximo alcance
	 * 
	 * @param indice indice del jugador al que hay que calcular la posicion
	 * @param posicionBalon posicion actual del balon en el campo
	 * @return la posicion a la que se debe dirigir el jugador
	 */
	private Posicion movimientoBascularJugador(int indice, Posicion posicionBalon) {
		double dividendoX;
		double dividendoY;
		
		// calculamos parte de la formula dependiendo del cuadrante que ocupa en el campo
		if(posicionBalon.getX() > 0)
			dividendoX = zonasRU[indice].getX() - alineacionEnJuego[indice].getX();
		else
			dividendoX = alineacionEnJuego[indice].getX() - zonasLD[indice].getX(); 
		
		if(posicionBalon.getY() > 0)
			dividendoY = zonasRU[indice].getY() - alineacionEnJuego[indice].getY();
		else
			dividendoY = alineacionEnJuego[indice].getY() - zonasLD[indice].getY();
			
		// calculamos la posicion en funcion de lo que el balon se separe del centro del campo
		// y de una zona de infuencia del jugador
		double posicionX = alineacionEnJuego[indice].getX() + 
		((dividendoX / (Constantes.ANCHO_CAMPO_JUEGO / 2)) * posicionBalon.getX());
		double posicionY = alineacionEnJuego[indice].getY() + 
		((dividendoY / (Constantes.LARGO_CAMPO_JUEGO / 2)) * posicionBalon.getY());

		return new Posicion(posicionX, posicionY);
	}

	/**
	 * Esta funcion calcula la posicion que deber� ocupar el portero en el campo.
	 * Se situa siempre tratando de cubrir la mayor cantidad de porteria posible
	 * @param posicionBalon
	 * @return
	 */
	private Posicion movimientoBascularPortero(Posicion posicionBalon) {
		Posicion posicionPaloIzdo = Constantes.posteIzqArcoSup;
		Posicion posicionPaloDcho = Constantes.posteDerArcoSup;

		double posicionX;
		double posicionY =
			(
					(Constantes.ANCHO_AREA_GRANDE / Constantes.LARGO_CAMPO_JUEGO) * 
					(posicionBalon.getY() +  Constantes.LARGO_CAMPO_JUEGO / 2)) - 
			Constantes.LARGO_CAMPO_JUEGO / 2;
		
		// calcular las direcciones a los palos
		double direccionPaloIzdo = calcularDireccion(posicionBalon, Constantes.posteIzqArcoInf);
		double direccionPaloDcho = calcularDireccion(posicionBalon, Constantes.posteDerArcoInf);;

		try {
			posicionX = posicionBalon.getX() + (
					(posicionY - posicionBalon.getY()) /
					Math.tan((direccionPaloIzdo + direccionPaloDcho) / 2));
		} 
		catch (ArithmeticException e) {
			// en el caso de que el balon este en frente justo del centro de la porteria
			// si esta a la izda de la porteria, se pone en el palo izquierdo
			if(posicionBalon.getX() < 0)
				posicionX = posicionPaloIzdo.getX();
			// si esta a la dcha de la porteria, se pone en el palo derecho
			else if(posicionBalon.getX() > 0)
				posicionX = posicionPaloDcho.getX();
			// si esta a la altura del centro de la la porteria, se pone en el centro
			else
				posicionX = 0;
		}

		return new Posicion(posicionX, posicionY);
	}
	
	private class Posicion3D {
		
		private Posicion posicion;
		private double altura;
		private int iteracion;

		public Posicion3D(Posicion posicion, double altura, int iteracion) {
			this.posicion = posicion;
			this.altura = altura;
			this.iteracion = iteracion;
		}
		
		public Posicion getPosicion() {
			return posicion;
		}
		public double getAltura() {
			return altura;
		}
		public int getIteracion() {
			return iteracion;
		}
		
		public String toString() {
			return posicion + ":" + altura + "@" + iteracion; 
		}
	}
}